0x1. 新生代

触发Minor GC的条件:当新生代中存储对象Eden区满时,将触发其垃圾回收机制清理不再用对象,所以Minor GC具有随机性。

0x11. Eden Space

“**The pool from which memory is initially allocated for most objects.**“

主要占新生代空间大小的80%。通常认为,新生代主要存储的为JVM新创建初始化的Object(对象),其一般占用堆空间的1/3大小,其新生代又包含了三个区分别为:一个Eden区和两个Survivor区。其中大部分new出来或者生成出来的对象都是在Eden区的。当Eden区满了的时候,将会执行Minor GC回收并会从Eden区中复制仍然存活的对象存储到两个Survivor区中的其中一个,而当Survivor区满了的时候,将从该区内复制仍然存活的对象至Tenured(年老)区即老年代中

0x12. Survivor Space

垃圾回收复制算法思想将原有的内存空间划分成两块,每次只使用其中一块,在垃圾回收的时候,将正在使用的内存中的存活对象复制到另一块内存区域中,然后清除正使用过的内存区域,交换两个区域的角色,完成垃圾回收。

主要占新生代空间大小的20%。经查阅资料,发现其空间主要用于缓存MinorGC回收过程中幸存下来的对象,当出发Minor GC时,将存活对象从Eden区复制到Survivor其中一个区进行碎片整理,最后清空Eden区和刚刚使用过的一个Survivor区

为什么需要有两个Survivor Space

  • 当没有Survivor Space存储这些在Eden区幸存下来的对象,则这些幸存对象都将转移到Tenured(年老)区,而因为新生代Eden区会频繁地进行对象的创建,则Tenured(年老)区将很快地被占满从而导致进行一次Major GC回收,而一次Minor GC + 一次Major GC ≈ 一次Full GC,将导致长时间地内存回收,从而影响整个系统的性能
  • 当只有一个Survivor Space时,将产生一个问题,当Eden区Survivor区满时,此时进行Minor GC,则Eden区Survivor区都各有一些存活的对象,此时将Eden区的对象强行转移到Survivor区的话,很明显其两部分对象所占的内存是不连续的,进而就导致了产生内存碎片化,其碎片化带来的影响和风险都是极大的,严重地影响了Java的整体性能,因此应景而生地有了两个Survivor Space用于解决碎片化冲突

1. Survivor From

用于存储上一次GC的幸存者,作为这一次GC的被扫描者

2. Survivor To

用于临时保存一次Minor GC过程中的幸存者,解决碎片化冲突

0x2. 年老代(Tenured Generation)

触发Major GC的条件:

  1. 触发Major GC通常用于清理Tenured区,主要用于回收年老代中的对象,且在出现Major GC时通常会出现至少一次Minor GC。
  2. 当无法找到一块较大的连续空间分配给新创建的较大对象时,也会触发Major GC进行垃圾回收,以便容纳该对象。

年老代其存储的通常是经过多次GC后仍然存活的对象,此区域的对象相对比较稳定,因此,触发垃圾回收次数相对较少,可以认为年老代中存储的均是生命周期较长的对象。Major GC采用的是标记-清理算法(或者标记-整理算法)。Major GC每次触发时,都将整体地扫描区域内的对象,进行标记后再删除,同时此过程将产生内存碎片,需要重新进行整理,因此Major GC耗时长。当Tenured Generation也满了的时候,将会由JVM抛出OOM(Out Of Memory)异常。

0x3. 永久代(Permanent Generation)

触发Full GC的条件:

  1. 由系统主动调用System.gc()方法时,系统将建议执行Full GC,但是并不是一定执行。
  2. 年老代的空间不足使用。
  3. 方法区的空间不足使用。
  4. 当永久代空间满时将会触发Full GC,会导致Class,元数据等信息的卸载。

方法区(method area)只是JVM规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,具体放在哪里,不同的实现可以放在不同的地方。而永久代Hotspot虚拟机特有的概念,是方法区的一种实现,别的JVM都没有这个东西。

在Java 6中,方法区中包含的数据,除了JIT编译生成的代码存放在native memory的CodeCache区域,其他都存放在永久代;
在Java 7中,Symbol的存储从PermGen移动到了native memory,并且把静态变量从instanceKlass末尾(位于PermGen内)移动到了java.lang.Class对象的末尾(位于普通Java heap内);
在Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间(Metaspace),‑XX:MaxPermSize 参数失去了意义,取而代之的是-XX:MaxMetaspaceSize。

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

对于习惯在HotSpot虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。对于其他虚拟机(如BEA JRockit、IBM J9等)来说是不存在永久代的概念的。即使是HotSpot虚拟机本身,根据官方发布的路线图信息,现在也有放弃永久代并“搬家”至Native Memory来实现方法区的规划了。

Java虚拟机规范对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。在Sun公司的BUG列表中,曾出现过的若干个严重的BUG就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。
根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

以上描述截取自:
《深入理解Java虚拟机:JVM高级特性与最佳实践》 作者: 周志明

永久代可以理解为是内存中永久保存的区域,主要用于存放Class和Meta(元数据)信息,Class在被加载的时候也将被放入永久区域中,它和存放对象的区域不同,因为该区域不会在主程序运行时进行垃圾回收操作。所以这也导致了永久代区域会随着加载的Class增多而膨胀,且在到达临界值后抛出OOM(Out Of Memory)异常

虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。而对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 **-XX:MaxTenuringThreshold (阈值)**来设置。

在Java 8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。

而元空间与永久代本质上其实差不多,最大的区别就是:元空间使用的是本地内存,而永久代使用的是Java虚拟机内存,因此通常情况下元空间的大小取决于本地内存大小。